package com.gwtt.simulator.netconf.subsystem;

import com.gwtt.simulator.netconf.framing.EndOfMessageFramingMechanism;
import com.gwtt.simulator.netconf.framing.NetconfEncoder;
import com.gwtt.simulator.netconf.model.hello.Hello;
import com.gwtt.simulator.netconf.util.Constants;
import com.gwtt.simulator.netconf.util.XStreamUtil;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.sshd.common.future.SshFutureListener;
import org.apache.sshd.common.io.IoInputStream;
import org.apache.sshd.common.io.IoOutputStream;
import org.apache.sshd.common.io.IoReadFuture;
import org.apache.sshd.common.io.IoWriteFuture;
import org.apache.sshd.common.util.buffer.Buffer;
import org.apache.sshd.common.util.buffer.ByteArrayBuffer;

import java.util.Random;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import static java.nio.charset.StandardCharsets.UTF_8;

@Data
@Slf4j
public class NetconfAsyncClient {

    private NetconfEncoder encoder = new EndOfMessageFramingMechanism();
    private NetconfMessageState state = NetconfMessageState.NO_MATCHING_PATTERN;
    private Lock lock = new ReentrantLock();

    private String id;
    private IoInputStream in;
    private IoOutputStream out;

    public void read() {
        StringBuilder msg = new StringBuilder();
        ByteArrayBuffer buffer = new ByteArrayBuffer(1024);
        in.read(buffer).addListener(new SshFutureListener<IoReadFuture>() {
            @Override
            public void operationComplete(IoReadFuture future) {
                Buffer buffer = future.getBuffer();
                byte[] bytes = buffer.array();
                if (log.isDebugEnabled()) {
                    log.debug("{} receive message: {}", id, new String(bytes, 0, buffer.available()));
                }
                for (int i = 0; i < buffer.available(); i++) {
                    char c = (char) bytes[i];
                    state = state.evaluateChar(c);
                    msg.append(c);
                    if (state == NetconfMessageState.END_PATTERN) {
                        String body = msg.toString().replace(Constants.MESSAGE_END_MARK, "");
                        NetconfMessageBus.addMessage(new NetconfMessage(NetconfAsyncClient.this, body));
                        log.info("receive message: {}", body);
                        msg.delete(0, msg.length());
                    } else if (state == NetconfMessageState.END_CHUNKED_PATTERN) {
                        String body = msg.toString().replaceAll(Constants.MSGLEN_REGEX_PATTERN, "");
                        body = body.replaceAll(Constants.CHUNKED_END_REGEX_PATTERN, "");
                        NetconfMessageBus.addMessage(new NetconfMessage(NetconfAsyncClient.this, body));
                        log.info("receive message: {}", body);
                        msg.delete(0, msg.length());
                    }
                }
                buffer.clear(true);
                in.read(buffer).addListener(this);
            }
        });
    }

    public boolean write(String rpc) {
        if (lock.tryLock()) {
            try {
                log.info("write message {}", rpc);
                String encoded = encoder.encode(rpc);
                ByteArrayBuffer buffer = new ByteArrayBuffer(encoded.getBytes(UTF_8));
                int size = encoded.length();
                IoWriteFuture future = out.writePacket(buffer);
                future.addListener(f -> {
                    log.info("{} write message(size: {}) successfully", this.id, size);
                    lock.unlock();
                });
            } catch (Exception e) {
                log.error("failed to write message: {} to {}", rpc, this.id, e);
            } finally {
                lock.unlock();
            }
            return true;
        } else {
            log.warn("could not get lock, try next time");
            return false;
        }
    }

    public void sendHelloMessage() {
        Hello hello = new Hello();
        hello.setSessionId(new Random().nextInt(10000));
        String s = XStreamUtil.toXML(hello);
        String helloMessage = encoder.encode(s);
        try {
            ByteArrayBuffer buffer = new ByteArrayBuffer(helloMessage.getBytes(UTF_8));
            out.writePacket(buffer).addListener(f -> log.info("write hello message to {} successfully", this.id));
        } catch (Exception e) {
            log.error("failed to write hello message {}", this.id, e);
        }
    }
}
